package com.ap.core.business.message.service.impl;

import com.ap.core.backoffice.domain.BackOfficeOrderPayment;
import com.ap.core.business.info.ValidationInfo;
import com.ap.core.business.message.additionalinfo.builder.AdditionalInfobuilder;
import com.ap.core.business.message.domain.translators.BindingListener;
import com.ap.core.business.message.domain.translators.BindingMetaData;
import com.ap.core.business.message.domain.translators.ReconciliationInfo;
import com.ap.core.business.message.domain.translators.Translator;
import com.ap.core.business.message.ldap.handler.LdapHandler;
import com.ap.core.business.message.response.translators.ResponseMetaData;
import com.ap.core.business.message.response.translators.ResponseTranslator;
import com.ap.core.business.message.response.translators.listener.ResponseListener;
import com.ap.core.business.message.validation.handler.ValidationHandler;
import com.ap.core.business.message.validation.listener.ValidationListener;
import com.ap.core.common.types.EventTypeCode;
import com.ap.core.common.types.ValidationStatus;
import com.ap.core.dao.ReconciliationParameterDao;
import com.ap.core.domain.*;
import com.ap.core.exception.BusinessFailureException;
import com.ap.core.message.info.BaseMessageInfo;
import com.ap.core.message.info.MessageHeaderInfo;
import com.ap.core.message.info.RequestMessageInfo;
import com.ap.core.message.service.CoreResponseInfo;
import com.ap.core.message.service.MessageProcessor;
import com.ap.core.transaction.APTransactionInfo;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;

/**
 * User: Ranjit.Singh
 * Implementation class for {@link MessageProcessor}
 */
public class DefaultMessageProcessor<T,AI> implements MessageProcessor<RequestMessageInfo<MessageHeaderInfo<T>>, BaseMessageInfo,AI> {

    private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageProcessor.class);

    private Map<String, Translator<BaseMessageInfo>> domainTranslators;
    private Map<String, ValidationHandler<RequestMessageInfo<MessageHeaderInfo<T>>>> validationHandlers;
    private Map<String, LdapHandler<RequestMessageInfo<MessageHeaderInfo<T>>>> ldapHandlers;
    private Map<String, AdditionalInfobuilder<RequestMessageInfo<MessageHeaderInfo<T>>,AI>> additionalInfobuilders;
    private Map<String, ResponseTranslator<RequestMessageInfo<MessageHeaderInfo<T>>, BaseMessageInfo>> responseTranslators;
    private ReconciliationParameterDao reconciliationParameterDao;



    /**
     *  {@inheritDoc}
     *  This method is the entry point to start the data base transaction.
     * @param request  - request object to be processed
     * @param response - response object returned
     * @return  - response message returned.
     */
    @Override
    @Transactional
    public CoreResponseInfo<BaseMessageInfo, AI> process(final RequestMessageInfo<MessageHeaderInfo<T>> request, final BaseMessageInfo response) throws BusinessFailureException {

        final MessageProcessingEngine messageProcessingEngine = new MessageProcessingEngine(request, response);

        return messageProcessingEngine.processMessage();
    }


    private class MessageProcessingEngine implements BindingListener, ValidationListener, ResponseListener{

        private ApMessage apMessage;
        private final RequestMessageInfo<MessageHeaderInfo<T>> request;
        private BaseMessageInfo response;
        private final ResponseMetaData responseMetaData;
        private final BindingMetaData bindingMetaData;

        /**
         * Default constructor for this class
         * @param request  - request object to be processed
         * @param response - response object returned
         */
        private MessageProcessingEngine(final RequestMessageInfo<MessageHeaderInfo<T>> request, final BaseMessageInfo response){
            this.request = request;
            this.response = response;
            this.bindingMetaData = new BindingMetaData();
            final LocalDateTime currentDateTime = new LocalDateTime();
            this.bindingMetaData.setReconciliationInfo(buildReconciliationInfo(currentDateTime));           
            this.responseMetaData = new ResponseMetaData();
        }

        /**
         *  Process request message for translation, validation and response creation
         * @return - response to processed request
         */

        CoreResponseInfo<BaseMessageInfo, AI> processMessage() throws BusinessFailureException{

             LOG.debug("Start processing request with message id :" + request.getMsgHdr().getMsgId() + "at time :" + new LocalDateTime());
             //populate Msg Header of response
             responseTranslators.get(response.getClass().getSimpleName() +"-HEADER").populateResponse(request, response, bindingMetaData, responseMetaData, this);

             //populate message response object
             responseTranslators.get(response.getClass().getSimpleName()).populateResponse(request, response, bindingMetaData, responseMetaData, this);

             if(null != ldapHandlers.get(request.getClass().getSimpleName())) {
                ldapHandlers.get(request.getClass().getSimpleName()).handleLdapEntry(request, bindingMetaData.getZappId());
             }

             LOG.debug("Returning response for request received with message id :" + request.getMsgHdr().getMsgId() + "at time :" + new LocalDateTime());

            return new CoreResponseInfo<BaseMessageInfo, AI>() {
                @Override
                public BaseMessageInfo getResponse() {
                    return response;
                }

                @Override
                public AI getAdditionalInfo() {
                    AI additionalInfo = null;
                    if(null != additionalInfobuilders.get(request.getClass().getSimpleName())) {
                        additionalInfo = additionalInfobuilders.get(request.getClass().getSimpleName()).build(request, bindingMetaData);
                    }
                    return additionalInfo;
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onMessageHeaderResponse(BaseMessageInfo response) throws BusinessFailureException {
            this.response = response;

            //Translate Msg Header
            domainTranslators.get(request.getClass().getSimpleName()+ "-HEADER").translate(request, bindingMetaData, this);

        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onApMessage(ApMessage apMessage, OrderPayment originalReadOnlyOrderPayment) throws BusinessFailureException {
            this.apMessage = apMessage;
            bindingMetaData.setApMessageId(apMessage.getId());
            bindingMetaData.setOriginalReadOnlyOrderPayment(originalReadOnlyOrderPayment);
            responseMetaData.setOriginalReadOnlyOrderPayment(originalReadOnlyOrderPayment);
            bindingMetaData.setOriginalOrderPaymentId(null == originalReadOnlyOrderPayment ? null : originalReadOnlyOrderPayment.getId());

            // Validate request Message
            validationHandlers.get(request.getClass().getSimpleName()).validate(request, bindingMetaData, this);
        }
        
        
        @Override
		public void onApMessageBackOffice(ApMessage apMessage, BackOfficeOrderPayment backOfficeOrderPayment) throws BusinessFailureException {
        	 this.apMessage = apMessage;
             bindingMetaData.setApMessageId(apMessage.getId());
             if(EventTypeCode.TRANSITION.equals(apMessage.getInputMessageType().getInputEventTypeCode())){
            	 bindingMetaData.setBackOfficeOrderPayment(backOfficeOrderPayment);
             }

             // Validate request Message
             validationHandlers.get(request.getClass().getSimpleName()).validate(request, bindingMetaData, this);
		}

        /**
         * {@inheritDoc}
         */
        @Override
        public void onValidMessage() throws BusinessFailureException {
            domainTranslators.get(request.getClass().getSimpleName()).translate(request, bindingMetaData, this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onOrderPayment(OrderPayment orderPayment, APTransactionInfo apTransaction){
            responseMetaData.setApTransaction(apTransaction);         
            responseMetaData.setReconciliationId(orderPayment.getReconciliationCycleId());
            responseMetaData.setMobileAppIntentId(orderPayment.getAppIntent());
            final String brn = orderPayment.getBasketReferenceNumber();
            responseMetaData.setBasketReferenceNumber(brn);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onZappUser(ZappUser zappUser, String verificationId) {
            bindingMetaData.setZappId(zappUser.getId());
            bindingMetaData.setVerificationId(verificationId);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onOrderPaymentRefund(OrderPaymentRefund orderPaymentRefund, BackOfficeOrderPayment originalOrderPayment){
            responseMetaData.setOrderPaymentRefund(orderPaymentRefund);
            responseMetaData.setBackOfficeOrderPayment(originalOrderPayment);
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        public void onRejectedMessage(List<ValidationInfo> errorCodesInfo) throws BusinessFailureException {
            handleMessageFailure(errorCodesInfo);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onMessageResponse(BaseMessageInfo response) {
            this.response = response;
        }

        private void handleMessageFailure(List<ValidationInfo> errorCodesInfo) throws BusinessFailureException {
            apMessage.setValidationStatus(ValidationStatus.REJECTED);
            apMessage.setValidationTimeStamp(new LocalDateTime());
            String internalErrorCode = errorCodesInfo.get(0).getInternalBusinessErrorCode();
            LOG.info("ErrorCodeInfo ="+internalErrorCode);
            throw new BusinessFailureException(String.format("Business Rule failed With the InternalErrorCode=%s", internalErrorCode), errorCodesInfo);
        }

    }

    public void setResponseTranslators(Map<String, ResponseTranslator<RequestMessageInfo<MessageHeaderInfo<T>>, BaseMessageInfo>> responseTranslators) {
        this.responseTranslators = responseTranslators;
    }

    public void setDomainTranslators(Map<String, Translator<BaseMessageInfo>> domainTranslators) {
        this.domainTranslators = domainTranslators;
    }

    public void setValidationHandlers(Map<String, ValidationHandler<RequestMessageInfo<MessageHeaderInfo<T>>>> validationHandlers) {
        this.validationHandlers = validationHandlers;
    }

    
    private ReconciliationInfo buildReconciliationInfo(final LocalDateTime currentDateTime){
    	ReconciliationInfo reconciliationInfo = new ReconciliationInfo();
        //Generate it RECON[DDMMYYYY][SEQ]
        final SimpleDateFormat dateFormat = new SimpleDateFormat("ddMMyyyy");
        final ReconciliationParameter reconciliationParameter = reconciliationParameterDao.getCurrentCycle(currentDateTime);
        StringBuilder builder = new StringBuilder("RECON");
        builder.append(dateFormat.format(currentDateTime.toLocalDate().toDateMidnight().toDate()));
        String sequence = String.valueOf(reconciliationParameter.getId());
        builder.append(StringUtils.leftPad(sequence, 2, "0"));
        reconciliationInfo.setReconciliationCycleId(builder.toString());
        reconciliationInfo.setReconciliationDate(currentDateTime.toLocalDate());
        //Note: reconCutoffEndTimeStmp takes the current day and hours/min/seconds details from the reconciliationParameter CycleEndTimeStmp
        LocalDateTime reconCutoffEndTimeStmp = new LocalDateTime(currentDateTime.getYear(), currentDateTime.getMonthOfYear(),  currentDateTime.getDayOfMonth(),  
        														 reconciliationParameter.getCycleEndTimeStmp().getHourOfDay(),  
        														 reconciliationParameter.getCycleEndTimeStmp().getMinuteOfHour(),  
        														 reconciliationParameter.getCycleEndTimeStmp().getSecondOfMinute(),  
        														 reconciliationParameter.getCycleEndTimeStmp().getMillisOfSecond());
        reconciliationInfo.setReconCutoffEndTimeStmp(reconCutoffEndTimeStmp.plusSeconds(reconciliationParameter.getReconCutoffGraceSeconds()));
        return reconciliationInfo;
    }

    public void setReconciliationParameterDao(ReconciliationParameterDao reconciliationParameterDao) {
        this.reconciliationParameterDao = reconciliationParameterDao;
    }

    public void setLdapHandlers(Map<String, LdapHandler<RequestMessageInfo<MessageHeaderInfo<T>>>> ldapHandlers) {
        this.ldapHandlers = ldapHandlers;
    }

    public void setAdditionalInfobuilders(Map<String, AdditionalInfobuilder<RequestMessageInfo<MessageHeaderInfo<T>>, AI>> additionalInfobuilders) {
        this.additionalInfobuilders = additionalInfobuilders;
    }
}